package lil.llahn.security.common.symmetry;

import lil.llahn.security.common.asymmetric.Types;
import lil.llahn.security.common.utils.Base64;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Random;

/**
 * @ClassName: PBESecurityTools
 * @Description: TODO Password Base Encryption - 基于口令加密
 *  PBE算法没有密钥的概念，把口令当做密钥了。因为密钥长短影响算法安全性，还不方便记忆，这里我们直接换成我们自己常用的口令就大大不同了，便于我们的记忆。
 *  但是单纯的口令很容易被字典法给穷举出来，所以我们这里给口令加了点“盐”，这个盐和口令组合，想破解就难了。
 *  同时我们将盐和口令合并后用消息摘要算法进行迭代很多次来构建密钥初始化向量的基本材料，使破译更加难了。
 * @Date: 2019/11/11 09:11
 * @Auther: lil
 */
public class PBESecurityTools {
    private final static int SALT_COUNT = 100;

    /**
     * todo 初始化 盐
     * @return
     */
    public static byte[] init() {
        byte[] salt = new byte[8];
        Random random = new Random();
        random.nextBytes(salt);
        return salt;
    }

    /**
     * todo 转换秘钥
     * @param key 字符串
     * @return
     */
    public static Key stringToKey(String key) {
        SecretKey secretKey = null;
        try {
            PBEKeySpec keySpec = new PBEKeySpec(key.toCharArray());
            SecretKeyFactory factory = SecretKeyFactory.getInstance(Types.PBE.PBEWithSHA1AndRC2_40.name());
            secretKey = factory.generateSecret(keySpec);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return secretKey;
    }

    /**
     * todo 加密
     * @param data 待加密字节数组
     * @param key  秘钥
     * @param salt 盐
     * @return
     */
    public static String encryptPBE(String data, String key, byte[] salt) {
        try {
            // 获取密钥
            Key k = stringToKey(key);
            PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, SALT_COUNT);
            Cipher cipher = Cipher.getInstance(Types.PBE.PBEWithSHA1AndRC2_40.name());
            cipher.init(Cipher.ENCRYPT_MODE, k, parameterSpec);
            return Base64.encoderToString(cipher.doFinal(data.getBytes()));
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * todo 解密
     * @param data 待解密字节数组
     * @param key  秘钥
     * @param salt 盐
     * @return
     */
    public static String decryptPBE(String data, String key, byte[] salt) {
        try {
            // 获取密钥
            Key k = stringToKey(key);
            PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, SALT_COUNT);
            Cipher cipher = Cipher.getInstance(Types.PBE.PBEWithSHA1AndRC2_40.name());
            cipher.init(Cipher.DECRYPT_MODE, k, parameterSpec);
            return new String( cipher.doFinal(Base64.decoderToByte(data)));
        }catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }
}
